import {
    isObject,
    forEach,
    merge,
    isArray,
    cloneDeep,
    get,
    isNil,
    isNumber,
    isEmpty,
    hasIn,
    set,
    each,
    isString
} from 'lodash-es';
import type {
    UnionSpecItem,
    ListSpecItem,
    AlternativeSpecItem,
    ObjectSpecItem,
    EnumValuesSpecItem,
    ExpressionProps
} from '../interface';
import {parseDefaultValue} from './helper';
import {getFieldPath} from '../path';
import type {ValuesState} from '../hooks/types';

interface ExpressionMap {
    [path: string]: {
        spec: UnionSpecItem;
        removeOn: (rootValue: any, parentValue: any, currentPath: string, get: any, getJsonPath: any) => boolean;
    };
}

function getInitValueByListItem(spec: ListSpecItem, value?: any) {
    if (Array.isArray(value) && isEmpty(value)) {
        return value;
    }
    let defaultValue = parseDefaultValue(spec.defaultValue);
    defaultValue = isArray(defaultValue) && defaultValue;
    value = isArray(value) && value;

    const actualValue = value || defaultValue || [];
    const initialCount = (spec.extraAttr || {}).initialCount;
    const {minCount = 1} = spec.rules || {};
    const count = actualValue.length || (initialCount != null ? initialCount : minCount);

    const result: any[] = [];

    for (let i = 0; i < count; i++) {
        if (spec.element) {
            result.push(getInitValueBySpecItem(spec.element, actualValue[i]));
        } else if (spec.items) {
            result.push(getInitValueWithoutExpression(spec.items, actualValue[i]));
        }
    }

    return result;
}

function findAlternativeEnumValue(spec: AlternativeSpecItem, value: any) {
    let selectedKey = '';
    if (isNil(value)) {
        return null;
    }
    if (isObject(value)) {
        selectedKey = Object.keys(value)[0];
    } else {
        selectedKey = `${value}`;
    }
    if (selectedKey) {
        const enumValue = spec.enumValues!.find((item) => {
            return `${selectedKey}` === item.value;
        });
        if (enumValue) {
            return enumValue;
        }
    }
    return null;
}

function getInitValueByAlternativeItem(spec: AlternativeSpecItem, value?: any) {
    const result: {[index: string]: any} = {};
    const defaultValue = parseDefaultValue(spec.defaultValue);

    const enumValue =
        (value && findAlternativeEnumValue(spec, value)) ||
        (defaultValue && findAlternativeEnumValue(spec, defaultValue));

    if (enumValue) {
        const selectedKey: string = enumValue.value;
        const childValue = !isObject(value) ? {} : get(value, selectedKey);
        const childDefaultValue = !isObject(defaultValue) ? {} : get(defaultValue, selectedKey);

        result[selectedKey] = getInitValueWithoutExpression(enumValue.items, merge(childDefaultValue, childValue));
    }

    return result;
}

function getInitValueByObjectItem(spec: ObjectSpecItem, value?: any) {
    let defaultValue = parseDefaultValue(spec.defaultValue);
    if (!isObject(defaultValue)) {
        defaultValue = {};
    }
    if (!isObject(value)) {
        value = {};
    }

    const actualValue = merge(defaultValue, value);
    const result: {[index: string]: any} = {};

    forEach(spec.items, (item: UnionSpecItem) => {
        result[item.name] = getInitValueBySpecItem(item, actualValue[item.name]);
    });

    return result;
}

function getInitValueBySpecItem(spec: UnionSpecItem, value?: any): any {
    let defaultValue = spec.defaultValue;

    switch (spec.dataType) {
        case 'LIST':
            return getInitValueByListItem(spec, value);
        case 'ALTERNATIVE':
            return getInitValueByAlternativeItem(spec, value);
        case 'OBJECT':
            return getInitValueByObjectItem(spec, value);
        case 'BOOL':
        case 'NUMBER':
            // defaultValue 后端返回字符串, 这里对BOOL和NUMBER先尝试转一下，最后交给default处理
            defaultValue = parseDefaultValue(spec.defaultValue);
        case 'ENUM':
            // enum 多选类型的，defaultValue 后端返回字符串 要转换一下
            // 普通单选类型的，不需要转换
            if (get(spec, 'ui') === 'CheckboxGroup') {
                defaultValue = parseDefaultValue(spec.defaultValue);
            }
        default:
            if (value != null) {
                return value;
            }
            if (defaultValue != null) {
                return defaultValue;
            }
            if (spec.dataType === 'NUMBER') {
                return 0;
            }
            if (spec.dataType === 'BOOL') {
                return false;
            }
            if (spec.dataType === 'ENUM' && spec.ui === 'CheckboxGroup') {
                return [];
            }
            if (spec.dataType === 'UPLOAD' && spec.extraAttr?.multiple) {
                return [];
            }
            return '';
    }
}

export const handleExpression = (code: string) => {
    return new Function(
        'rootValue',
        'parentValue',
        'currentPath',
        'get',
        'getJsonPath',
        `try {
            return !!(${code});
        }
        catch (e) {
            console.log(e);
            console.log(rootValue, parentValue, currentPath);
        }`
    );
};

const expressionOperation = {
    LT: '<',
    GT: '>',
    LTOREQUAL: '<=',
    GTOREQUAL: '>=',
    EQUAL: '==',
    NOT_EQUAL: '!=',
    INCLUDES: 'includes',
    NOT_INCLUDES: 'not_includes',
    BOOLEAN_TRUE: 'true',
    BOOLEAN_FALSE: 'false',
    EMPTY: 'empty',
    LENGTH: 'length'
};

function handleExpressionCompare(sourceValue: any, targetValue: any, operation: string): boolean {
    try {
        switch (operation) {
            // 小于
            case expressionOperation.LT:
                return sourceValue < targetValue;
            // 大于
            case expressionOperation.GT:
                return sourceValue > targetValue;
            // 小于等于
            case expressionOperation.LTOREQUAL:
                return sourceValue <= targetValue;
            // 大于等于
            case expressionOperation.GTOREQUAL:
                return sourceValue >= targetValue;
            // 等于
            case expressionOperation.EQUAL:
                // eslint-disable-next-line eqeqeq
                return sourceValue == targetValue;
            // 不等于
            case expressionOperation.NOT_EQUAL:
                // eslint-disable-next-line eqeqeq
                return sourceValue != targetValue;
            // 包含
            case expressionOperation.INCLUDES:
                // eslint-disable-next-line eqeqeq
                return sourceValue.find((v: any) => v == targetValue);
            // 不包含
            case expressionOperation.NOT_INCLUDES:
                // eslint-disable-next-line eqeqeq
                return !sourceValue.find((v: any) => v == targetValue);
            // 为空
            case expressionOperation.EMPTY:
                return isEmpty(sourceValue);
            // 布尔真
            case expressionOperation.BOOLEAN_TRUE:
                return Boolean(sourceValue) === true;
            // 布尔假
            case expressionOperation.BOOLEAN_FALSE:
                return Boolean(sourceValue) === false;
            /**
             * 取长度
             * targetValue如果是固定值，强转换targetValue为Number进行比较
             * targetValue如果是节点值，targetValue取值为节点的值的length属性
             */
            case expressionOperation.LENGTH:
                if (isArray(targetValue)) {
                    return sourceValue?.length === targetValue?.length;
                }
                return sourceValue?.length === Number(targetValue);
            default:
                throw new Error(`expression has no available operation: ${operation}`);
        }
    } catch (err) {
        console.error(err);
        return false;
    }
}

/** 表达式配置的逻辑操作枚举 */
export const expressionLogicOpEnum = {
    NULL: '-1',
    OR: '0',
    AND: '1'
};

/**
 * 解析表达式，返回表达式执行结果
 * @param status 表达式
 * @param values 表单的根value
 * @param fieldName 节点的fieldName
 * @returns 处理结果 布尔类型
 */
export const handleStatus = (status: string | ExpressionProps[] = '', values: ValuesState, fieldName: string) => {
    if (!status) {
        return false;
    }
    const rootValue = cloneDeep(values);
    if (isString(status)) {
        const parentPath = fieldName.split('.').slice(0, -1).join('.');
        const parentValue = get(rootValue, parentPath, rootValue);
        const statusFn = handleExpression(status);
        return !!statusFn(rootValue, parentValue, fieldName, get, getFieldPath);
    }
    if (isObject(status)) {
        const resultArr = status.reduce((prev: boolean[], st) => {
            const {operation, sourcePath, targetValue = '', targetPath = ''} = st;
            try {
                let newTargetValue = '';
                if (targetValue) {
                    newTargetValue = targetValue;
                } else if (targetPath) {
                    const newTargetPath = getFieldPath(fieldName, targetPath);
                    if (!newTargetPath) {
                        throw new Error(`unexpected expression targetPath: ${targetPath}`);
                    }
                    newTargetValue = get(rootValue, newTargetPath);
                }
                newTargetValue = targetValue || get(rootValue, targetPath);
                const newSoucePath = getFieldPath(fieldName, sourcePath);
                if (!newSoucePath) {
                    throw new Error(`unexpected expression sourcePath: ${sourcePath}`);
                }
                const sourceValue = get(rootValue, newSoucePath);
                prev.push(handleExpressionCompare(sourceValue, newTargetValue, operation));
                return prev;
            } catch (e: any) {
                console.error(e.message);
                prev.push(false);
                return prev;
            }
        }, []);

        let result: boolean = false;
        resultArr.forEach((r, index) => {
            if (index === 0) {
                result = r;
            } else {
                switch (status[index].logicOp) {
                    case expressionLogicOpEnum.AND:
                        result = result && r;
                        break;
                    case expressionLogicOpEnum.OR:
                        result = result || r;
                        break;
                }
            }
        });
        return result;
    }
    return false;
};

function collectExpressionByObject(spec: ObjectSpecItem, path: string, expressionMap: ExpressionMap) {
    forEach(spec.items, (item: UnionSpecItem) => {
        collectExpression(item, path ? `${path}.${item.name}` : item.name, expressionMap);
    });
}

function collectExpressionByAlternative(spec: AlternativeSpecItem, path: string, expressionMap: ExpressionMap) {
    forEach(spec.enumValues, (enumItem: EnumValuesSpecItem) => {
        const {value, items} = enumItem;
        forEach(items, (item: UnionSpecItem) => {
            collectExpression(item, path ? `${path}.${value}.${item.name}` : `${value}.${item.name}`, expressionMap);
        });
    });
}

function collectExpression(spec: UnionSpecItem, path: string, expressionMap: ExpressionMap) {
    const removeOn = spec[':remove'];

    if (removeOn) {
        expressionMap[path] = {
            // @ts-ignore
            removeOn: handleExpression(removeOn),
            spec: cloneDeep(spec)
        };
    }

    switch (spec.dataType) {
        case 'OBJECT':
            return collectExpressionByObject(spec, path, expressionMap);
        case 'ALTERNATIVE':
            return collectExpressionByAlternative(spec, path, expressionMap);
        default:
            break;
    }
}

export function collectExpressions(value: UnionSpecItem | UnionSpecItem[], path: string = '') {
    const expressionMap: ExpressionMap = {};
    const spec: UnionSpecItem[] = ([] as UnionSpecItem[]).concat(value);
    forEach(spec, (item: UnionSpecItem) => {
        collectExpression(item, path ? `${path}.${item.name}` : item.name, expressionMap);
    });

    return expressionMap;
}

// 用于获取不包含表达式的初始值
function getInitValueWithoutExpression(value?: UnionSpecItem | UnionSpecItem[], defaultValue: any = {}) {
    if (!value) {
        return;
    }
    const spec: UnionSpecItem[] = ([] as UnionSpecItem[]).concat(value);
    const result: {[index: string]: any} = {};
    forEach(spec, (item: UnionSpecItem) => {
        result[item.name] = getInitValueBySpecItem(item, defaultValue[item.name]);
    });
    return result;
}

/**
 * 获取root节点spec的数据
 * @param value root spec
 * @param defaultValue 默认值
 * @returns root value
 */
export function getInitValue(value?: UnionSpecItem | UnionSpecItem[], defaultValue: any = {}) {
    if (!value) {
        return;
    }
    const result = getInitValueWithoutExpression(value, defaultValue);
    if (!result) {
        return;
    }
    const expressionMap = collectExpressions(value);

    const keys = Object.keys(expressionMap);

    const processExpression = () => {
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            // 判断path是否在当前数据中存在
            if (!hasIn(result, key)) {
                continue;
            }
            const {removeOn, spec} = expressionMap[key];

            const keyArr: string[] = key.split('.');
            keyArr.pop();

            const parentKey = keyArr.join('.');
            const parentValue = parentKey ? get(result, parentKey) : result;

            const expressionResult = removeOn(result, parentValue, key, get, getFieldPath);

            if (parentValue && expressionResult) {
                try {
                    delete parentValue[spec.name];
                } catch (e) {
                    // eslint-disable-next-line no-console
                    console.log(e);
                }
            }
        }
    };
    processExpression();
    return result;
}
/**
 * 获取子节点初始化value
 * @param spec 子节点spec
 * @param defaultValue 子节点默认value
 * @param rootValue 根节点value
 * @param fieldName 子节点的根path
 * @returns 子节点数据
 */
export function getChildSpecInitValue(
    spec: UnionSpecItem | UnionSpecItem[],
    defaultValue: any = {},
    rootValue: any = {},
    fieldName: string = ''
) {
    if (!spec) {
        return;
    }
    // 获取子节点数据（不考虑表达式的影响）
    const result = getInitValueWithoutExpression(spec, defaultValue);
    if (!result) {
        return;
    }

    const pathArr = fieldName.split('.');
    pathArr.pop();
    const parentPath = pathArr.join('.');
    const rootValueWithChild = cloneDeep(rootValue);
    // 用子节点的数据更新root节点的数据
    each(result, (value: any, key: string) => {
        set(rootValueWithChild, parentPath ? `${parentPath}.${key}` : key, value);
    });
    // 收集子节点下的表达式，注意这里需要传递parentPath，这样表达式的path才是基于root的path
    const expressionMap = collectExpressions(spec, parentPath);
    const keys = Object.keys(expressionMap);

    const processExpression = () => {
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            // 判断path是否在当前数据中存在
            if (!hasIn(rootValueWithChild, key)) {
                continue;
            }
            const {removeOn, spec} = expressionMap[key];

            const keyArr: string[] = key.split('.');
            keyArr.pop();

            const parentKey = keyArr.join('.');
            const parentValue = parentKey ? get(rootValueWithChild, parentKey) : rootValueWithChild;
            const expressionResult = removeOn(rootValueWithChild, parentValue, key, get, getFieldPath);
            if (parentValue && expressionResult) {
                try {
                    delete parentValue[spec.name];
                } catch (e) {
                    // eslint-disable-next-line no-console
                    console.log(e);
                }
            }
        }
    };
    processExpression();
    return parentPath ? get(rootValueWithChild, parentPath) : rootValueWithChild;
}

// 用来创建LIST或者切换ALTERNATIVE的时候获取该项的初始值
export function getDefaultValue(spec: UnionSpecItem) {
    if (!spec) {
        return;
    }
    switch (spec.dataType) {
        case 'LIST':
            if (spec.element) {
                return getInitValueBySpecItem(spec.element!);
            }
            if (spec.items) {
                return getInitValueWithoutExpression(spec.items);
            }
            return;
        case 'ALTERNATIVE':
            if (spec.defaultValue == null) {
                return {};
            }
            return getInitValueByAlternativeItem(spec);
        case 'BOOL':
        case 'NUMBER':
            // defaultValue 后端返回字符串, 这里对BOOL和NUMBER先尝试转一下，最后交给default处理
            return parseDefaultValue(spec.defaultValue);
        case 'ENUM':
            // enum 多选类型的，defaultValue 后端返回字符串 要转换一下
            // 普通单选类型的，不需要转换
            if (get(spec, 'ui') === 'CheckboxGroup') {
                return parseDefaultValue(spec.defaultValue);
            }
            return spec.defaultValue;
        case 'OBJECT':
            return spec.defaultValue || {};
        default:
            return spec.defaultValue;
    }
}

const getSpecItem = (path: string, spec: UnionSpecItem[]): UnionSpecItem | EnumValuesSpecItem | null => {
    // 支持数组的通配符
    const pathArr = path.split('.');
    const localPath = pathArr[0];

    for (let i = 0; i < spec.length; i++) {
        const item = spec[i];
        const dataType = item.dataType;
        const name = item.name;

        if (name !== localPath) {
            continue;
        }

        if (pathArr.length === 1) {
            return item;
        }

        const subPathArr = pathArr.slice(1);
        const subPath = subPathArr[0];

        if (dataType === 'ALTERNATIVE' || dataType === 'ENUM') {
            const matchedEnumValue = item.enumValues!.find(
                (enumValue: EnumValuesSpecItem) => enumValue.value === subPath
            );

            if (subPathArr.length === 1 && matchedEnumValue) {
                return matchedEnumValue;
            }

            if (dataType === 'ALTERNATIVE' && matchedEnumValue && matchedEnumValue.items) {
                return getSpecItem(subPathArr.slice(1).join('.'), matchedEnumValue.items);
            }
        }

        if (dataType === 'OBJECT') {
            return getSpecItem(subPathArr.join('.'), item.items!);
        }

        if (dataType === 'LIST' && (isNumber(+subPath) || subPath === '[*]')) {
            if ((item.element && subPathArr.length === 1) || (item.items && subPathArr.length === 1)) {
                return item;
            }

            if (item.items) {
                return getSpecItem(subPathArr.slice(1).join('.'), item.items);
            }
        }
    }
    return null;
};

// 根据path获取对应的specItem

export function getSpecItemByPath(path: string, spec: UnionSpecItem | UnionSpecItem[]) {
    if (!spec || !path) {
        return null;
    }
    const localSpec = ([] as UnionSpecItem[]).concat(spec);
    return getSpecItem(path, localSpec);
}

// 更新spec
export function updateSpecItem(path: string, spec: UnionSpecItem[], updater: (el: UnionSpecItem) => void) {
    const target = getSpecItem(path, spec) as UnionSpecItem;
    if (updater && target) {
        updater(target);
    }
}
